package com.googlecode.gamedroid.event.source;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.googlecode.gamedroid.event.Event;
import com.googlecode.gamedroid.event.EventKey;
import com.googlecode.gamedroid.event.EventReceiver;
import com.googlecode.gamedroid.event.EventType;

public class DPad extends View implements EventSource
{
	private static final double PADDING = 0.1;
	
	private int mActivePointer = -1;
	private int mCenterX;
	private int mCenterY;
	private Event[] mEvents = {
		new Event(EventType.KEY, EventKey.BTN_0), // top
		new Event(EventType.KEY, EventKey.BTN_3), // right
		new Event(EventType.KEY, EventKey.BTN_1), // bottom
		new Event(EventType.KEY, EventKey.BTN_2) // left
	};
	private int mHeight = 0;
	private byte mLastPosition;
	private Paint mPaintArrow;
	private Paint mPaintBorder;
	private Path mPathArrow = new Path();
	private Path mPathClip = new Path();
	private Path mPathCross = new Path();
	private byte mPosition = 5; // look at your numpad
	private int mRadius;
	private EventReceiver mReceiver = null;
	private int mWidth = 0;
	
	public DPad(Context context)
	{
		super(context);
		onLoad();
	}
	
	public DPad(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		onLoad();
	}
	
	public DPad(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		onLoad();
	}
	
	private void onLoad()
	{
		setLayerType(View.LAYER_TYPE_SOFTWARE, null);
		
		mPaintBorder = new Paint();
		mPaintBorder.setColor(0xFFFFFFFF);
		mPaintBorder.setStyle(Paint.Style.STROKE);
		mPaintBorder.setStrokeWidth(2);
		
		mPaintArrow = new Paint();
		mPaintArrow.setColor(0xFFFF0000);
		mPaintArrow.setStyle(Paint.Style.FILL_AND_STROKE);
		mPaintArrow.setStrokeWidth(2);
	}
	
	private void sendEvent()
	{
		if(mPosition == mLastPosition)
			return;
		
		mLastPosition = mPosition;
		
		invalidate();
		
		if(mReceiver == null)
			return;
		
		mEvents[0].setValue((short) (mPosition >= 7 ? 1 : 0));
		mEvents[1].setValue((short) (mPosition % 3 == 0 ? 1 : 0));
		mEvents[2].setValue((short) (mPosition <= 3 ? 1 : 0));
		mEvents[3].setValue((short) ((mPosition + 2) % 3 == 0 ? 1 : 0));
		
		mReceiver.emit(mEvents, true);
		mReceiver.feedback();
	}
	
	private void transformCoordinate(float x, float y)
	{
		x -= mCenterX;
		y -= mCenterY;
		
		float radius = (float) (Math.sqrt(x * x + y * y) / mRadius);
		float angle = (float) ((Math.atan2(y, x) / Math.PI * 180 + 360 + 22.5) % 360);
		
		if(radius < 0.3)
			mPosition = 5;
		else
		{
			if(angle < 45)
				mPosition = 6;
			else if(angle < 90)
				mPosition = 3;
			else if(angle < 135)
				mPosition = 2;
			else if(angle < 180)
				mPosition = 1;
			else if(angle < 225)
				mPosition = 4;
			else if(angle < 270)
				mPosition = 7;
			else if(angle < 315)
				mPosition = 8;
			else
				mPosition = 9;
		}
	}
	
	@Override
	protected void onDraw(Canvas canvas)
	{
		canvas.drawARGB(0, 0, 0, 0);
		canvas.drawCircle(mCenterX, mCenterY, mRadius - 1, mPaintBorder);
		canvas.clipPath(mPathClip, Region.Op.REPLACE);
		canvas.drawPath(mPathCross, mPaintBorder);
		
		canvas.save();
		Paint paint;
		
		paint = mPosition >= 7 ? mPaintArrow : mPaintBorder;
		canvas.drawPath(mPathArrow, paint);
		
		canvas.rotate(90, mCenterX, mCenterY);
		
		paint = mPosition % 3 == 0 ? mPaintArrow : mPaintBorder;
		canvas.drawPath(mPathArrow, paint);
		
		canvas.rotate(90, mCenterX, mCenterY);
		
		paint = mPosition <= 3 ? mPaintArrow : mPaintBorder;
		canvas.drawPath(mPathArrow, paint);
		
		canvas.rotate(90, mCenterX, mCenterY);
		
		paint = (mPosition + 2) % 3 == 0 ? mPaintArrow : mPaintBorder;
		canvas.drawPath(mPathArrow, paint);
		
		canvas.restore();
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int w = getMeasuredWidth();
		int h = getMeasuredHeight();
		
		if(w < h)
			h = w;
		else
			w = h;
		
		int measuredW = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.getMode(getMeasuredWidthAndState()));
		int measuredH = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.getMode(getMeasuredHeightAndState()));
		
		setMeasuredDimension(measuredW, measuredH);
	}
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh)
	{
		super.onSizeChanged(w, h, oldw, oldh);
		
		mWidth = w;
		mHeight = h;
		
		mCenterX = mWidth / 2;
		mCenterY = mHeight / 2;
		
		mRadius = (int) (Math.min(mWidth, mHeight) / 2 * (1 - PADDING));
		
		mPathClip.reset();
		mPathClip.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CCW);
		
		float x1 = mCenterX - mRadius;
		float x2 = mCenterX - mRadius / 3;
		float x3 = mCenterX + mRadius / 3;
		float x4 = mCenterX + mRadius;
		float y1 = mCenterY - mRadius;
		float y2 = mCenterY - mRadius / 3;
		float y3 = mCenterY + mRadius / 3;
		float y4 = mCenterY + mRadius;
		
		mPathCross.reset();
		mPathCross.moveTo(x1, y2);
		mPathCross.lineTo(x2, y2);
		mPathCross.lineTo(x2, y1);
		mPathCross.moveTo(x3, y1);
		mPathCross.lineTo(x3, y2);
		mPathCross.lineTo(x4, y2);
		mPathCross.moveTo(x4, y3);
		mPathCross.lineTo(x3, y3);
		mPathCross.lineTo(x3, y4);
		mPathCross.moveTo(x2, y4);
		mPathCross.lineTo(x2, y3);
		mPathCross.lineTo(x1, y3);
		
		mPathArrow.reset();
		mPathArrow.moveTo(mCenterX, mCenterY - mRadius * 0.85f);
		mPathArrow.lineTo(mCenterX + mRadius * 0.15f, mCenterY - mRadius / 3f);
		mPathArrow.lineTo(mCenterX - mRadius * 0.15f, mCenterY - mRadius / 3f);
		mPathArrow.close();
	}
	
	@Override
	public Event[] getEventDefinitions()
	{
		return mEvents;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		int index;
		switch(event.getAction() & MotionEvent.ACTION_MASK)
		{
			case MotionEvent.ACTION_DOWN:
			case MotionEvent.ACTION_POINTER_DOWN:
				if(mActivePointer != -1)
					break;
				index = event.getActionIndex();
				mActivePointer = event.getPointerId(index);
				
				transformCoordinate(event.getX(index), event.getY(index));
				sendEvent();
				return true;
				
			case MotionEvent.ACTION_MOVE:
				if(mActivePointer == -1)
					break;
				index = event.findPointerIndex(mActivePointer);
				if(index == -1)
				{
					mActivePointer = -1;
					mPosition = 5;
					sendEvent();
					break;
				}
				
				transformCoordinate(event.getX(index), event.getY(index));
				sendEvent();
				return true;
				
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_POINTER_UP:
				if(mActivePointer == -1)
					break;
				index = event.findPointerIndex(mActivePointer);
				if(index != -1 && index != event.getActionIndex())
					break;
				
				mActivePointer = -1;
				mPosition = 5;
				sendEvent();
				return true;
		}
		return super.onTouchEvent(event);
	}
	
	@Override
	public void reset()
	{
		mActivePointer = -1;
		mPosition = 5;
		sendEvent();
	}
	
	@Override
	public void setEvents(short[] events)
	{
		if(events.length != 4)
			throw new IllegalArgumentException();
		
		for(byte i = 0; i < 4; i++)
			mEvents[i].setKey(events[i]);
	}
	
	@Override
	public void setReceiver(EventReceiver receiver)
	{
		mReceiver = receiver;
	}
}
